using Fusion.Utilities.Generics;
using Fusion.Utilities.Singleton;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Fusion.Async.Timers
{
    public class Timer
    {
        public enum InvokeType
        {
            Frame,
            Time
        }

        private float frequency = 1;
        private int count = 1;
        private int round = 0;
        private InvokeType type = InvokeType.Time;

        private Action action = null;
        private bool enabled = true;
        private bool firstImmediately = false; 
        private bool unscaledTime = false;

        private bool persistent = false;
        private string tag = "Default";

        public float Frequency { get => frequency; }
        public int Count { get => count;}
        public int Round { get => round; }
        public InvokeType Type { get => type; }
        public Action Action { get => action; }
        public bool Enabled { get => enabled; }
        public bool FirstImmediately { get => firstImmediately; }
        public bool UnscaledTime { get => unscaledTime; }
        public bool Persistent { get => persistent; }
        public string Tag { get => tag; }

        public void Pause()
        {
            enabled = false;
        }

        public void Play()
        {
            enabled = true;
        }

        /// <summary>
        /// 计时器执行频率 默认 1
        /// Type = Frame  frequency是帧数
        /// Type = Time frequency是秒数
        /// </summary>
        /// <param name="frequency"></param>
        /// <returns></returns>
        public Timer FrequencySetter(float frequency)
        {
            this.frequency = frequency;
            return this;
        }

        /// <summary>
        /// 计时器每一轮调用自定义方法的次数 默认 1
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        public Timer CountSetter(int count)
        {
            this.count = count;
            return this;
        }

        /// <summary>
        /// 计时器一共执行几轮
        /// Round 默认 0 循环执行
        /// </summary>
        /// <returns></returns>
        public Timer RoundSetter(int round)
        {
            this.round = round;
            return this;
        }

        /// <summary>
        /// 计时器类型
        /// Frame 隔帧执行
        /// Time 隔秒执行
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public Timer TypeSetter(InvokeType type)
        {
            this.type = type;
            return this;
        }

        /// <summary>
        /// 第一次是否直接执行
        /// 默认值false
        /// </summary>
        /// <returns></returns>
        public Timer FirstImmediatelySetter(bool value)
        {
            this.firstImmediately = value;
            return this;
        }

        /// <summary>
        /// 使用unscaledTime
        /// 默认值false
        /// </summary>
        /// <returns></returns>
        public Timer UnscaledTimeSetter(bool value)
        {
            this.unscaledTime = value;
            return this;
        }

        /// <summary>
        /// 持久化计时器，不会随着场景加载销毁
        /// 默认值false
        /// </summary>
        /// <returns></returns>
        public Timer PersistentSetter(bool value)
        {
            this.persistent = value;
            return this;
        }

        public Timer TagSetter(string value)
        {
            tag = value;
            return this;
        }

        /// <summary>
        /// 计时器开始执行某个方法，设置好参数后最后调用
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        public Timer Invoke(Action action)
        {
            this.action = action;
            TimerManager.Instance.Schedule(this);
            return this;
        }

        public void Stop()
        {
            Pause();
            TimerManager.Instance?.Stop(this);
        }
    }

    [DisallowMultipleComponent]
    public class TimerManager : SingletonBehaviour<TimerManager>
    {
        private class TimerData { 
            public Timer timer = null;
            public float frequencyCounter = 0;
            public float roundCounter = 0;
        }

        private OrderDictionary<Timer, TimerData> timerDatas = new OrderDictionary<Timer, TimerData>();
        void Update()
        {
            int length = timerDatas.Count;
            for (int index = length - 1; index >= 0; index--)
            {
                TimerData timerData = timerDatas.Get(index);
                if (timerData != null)
                {
                    HandleTimerData(timerData);
                }
            }
        }

        private void HandleTimerData(TimerData timerData)
        {
            if (timerData == null)
            {
                return;
            }

            Timer timer = timerData.timer;
            if (!timer.Enabled)
            {
                return;
            }
            if (timer.Type == Timer.InvokeType.Frame)
            {
                timerData.frequencyCounter++;
            }
            else if (timer.Type == Timer.InvokeType.Time)
            {
                timerData.frequencyCounter += timer.UnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
            }
            if (timerData.frequencyCounter >= timer.Frequency)
            {
                for (int index=0; index < timer.Count; index++)
                {
                    timer.Action();
                }
                timerData.frequencyCounter -= timer.Frequency;
                if (timer.Round > 0)
                {
                    timerData.roundCounter++;
                    if (timerData.roundCounter >= timer.Round)
                    {
                        timerData.timer.Stop();
                    }
                }
            }
        }

        public void Schedule(Timer timer)
        {
            if (!timerDatas.ContainsKey(timer))
            {
                TimerData timerData = new TimerData();
                timerData.timer = timer;
                if (timer.FirstImmediately)
                {
                    timerData.frequencyCounter = timer.Frequency;
                }
                timerDatas.Add(timer, timerData);
            }
        }

        public void Stop(Timer timer)
        {
            if (timer == null)
            {
                return;
            }

            timerDatas.Remove(timer);
        }

        public void Clear()
        {
            for (int index = timerDatas.Count - 1; index >= 0; index--)
            {
                TimerData timerData = timerDatas.Get(index);
                if (!timerData.timer.Persistent)
                {
                    timerData.timer.Stop();
                }
            }

            StopAllCoroutines();
        }

        public void Pause()
        {
            for (int index = timerDatas.Count - 1; index >= 0; index--)
            {
                timerDatas.Get(index).timer.Pause();
            }
        }

        public void Play()
        {
            for (int index = timerDatas.Count - 1; index >= 0; index--)
            {
                timerDatas.Get(index).timer.Play();
            }
        }

        public Coroutine InvokeNextFrame(Action action)
        {
            return StartCoroutine(InvokeNextFrameCoroutine(action));
        }

        private IEnumerator InvokeNextFrameCoroutine(Action action)
        {
            yield return null;
            if (action != null)
            {
                action();
            }
        }

        public Coroutine InvokeAfterTime(Action action, float time)
        {
            return StartCoroutine(InvokeAfterTimeCoroutine(action, time));
        }

        private IEnumerator InvokeAfterTimeCoroutine(Action action, float time)
        {
            yield return new WaitForSeconds(time);
            if (action != null)
            {
                action();
            }
        }

        public Coroutine InvokeEndOfFrame(Action action)
        {
            return StartCoroutine(InvokeEndOfFrameCoroutine(action));
        }

        private IEnumerator InvokeEndOfFrameCoroutine(Action action)
        {
            yield return new WaitForEndOfFrame();
            if (action != null)
            {
                action();
            }
        }
    }
}

